# geometric_chuck.py

from __future__ import division

import fractions

from ot_simulator.component import component

class geometric_chuck(component.component):
    '''The geometric chuck cuts spirograph figures on the work.
    
    The chuck spindle describes a circle twice as the chuck turns through one
    revolution.  The circle has a diameter of e centered at (e/2, 0).  The
    nosewheel rotates geared to the headstock index.  The nosewheel may rotate
    in the same direction as the headstock (``in``) or the opposite direction
    (``out``).

    When stacking multiple stages, connect the ``index_out`` of the first stage
    to the ``index`` of the next stage.

    ===============  ======================================
     Input setting   
    ===============  ======================================
    index (degrees)  from the headstock
    e                eccentricity setting
    n                gear ratio numerator
    d                gear ration denominator (default 1)
    direction        'in' or 'out', as per Bazley
    nosewheel_index  degrees, defaults to 0
    ===============  ======================================

    ===============  =======================================================
    Output setting
    ===============  =======================================================
    index_out        index of nosewheel relative to GC.  Tie this to the
                     *index* input setting of the next stage.
    ===============  =======================================================
    '''
    input_parameters = ('index', 'e', 'n', 'd', 'direction', 'nosewheel_index')

    def index_out(self, params):
        # Calculate nosewheel rotation caused by gearing
        n = self.get_param(params, 'n')
        if n == 0:
            index_out = 0.0
        else:
            index_out =   self.get_param(params, 'index')    \
                        * (self.get_param(params, 'd', 1) / n)

            # Reverse direction of spin if direction is 'out'
            if self.get_param(params, 'direction').lower() == 'out':
                index_out = -index_out

        # Add nosewheel_index
        index_out += self.get_param(params, 'nosewheel_index', 0)

        self.set_param(params, 'index_out', index_out)

        return index_out

    def get_transform(self, params, transform_in):
        e = float(self.get_param(params, 'e'))
        transform_out = transform_in.move(e)
        transform_out = transform_out.rotate_z(self.get_param(params,
                                                              'index_out'))
        return transform_out

def num_headstock_rotations(sequence_of_n_d):
    r'''The minimum number of headstock revolutions required to get a complete
    GC figure.

    The sequence_of_n_d is a sequence of (n, d) pairs.  Both values should be
    positive (direction is irrelevant).
    '''
    N = 1
    D = 1
    for n, d in sequence_of_n_d:
        if n:
            divisor = fractions.gcd(n, D)
            N *= n // divisor
            if d:
                D = D // divisor * d
    return N

def test():
    import doctest
    import sys
    sys.exit(doctest.testmod()[0])

if __name__ == "__main__":
    test()
